﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using UtilZ.Dotnet.Ex.Base;
using UtilZ.Dotnet.Ex.Exceptions;
using UtilZ.Dotnet.Ex.Model;
using UtilZ.Dotnet.WindowsDesktopEx.NativeMethod;

namespace UtilZ.Dotnet.WindowsDesktopEx.Base
{
    /// <summary>
    /// 窗口帮助类
    /// </summary>
    public class WindowHelper
    {
        /// <summary>
        /// 获取指定窗口句柄的类名
        /// </summary>
        /// <param name="hWnd">窗口句柄</param>
        /// <param name="maxCount">最大字符数</param>
        /// <returns></returns>
        public static string GetClassName(IntPtr hWnd, int maxCount = 512)
        {
            StringBuilder lpString = new StringBuilder(maxCount);
            DesktopNativeMethods.GetClassName(hWnd, lpString, lpString.Capacity);
            return lpString.ToString();
        }

        /// <summary>
        /// 获取指定窗口句柄的窗口标题
        /// </summary>
        /// <param name="hWnd">窗口句柄</param>
        /// <param name="option">字符编码选项</param>
        /// <returns></returns>
        public static string GetWindowTitle(IntPtr hWnd, EncodingOptions option)
        {
            int length = DesktopNativeMethods.GetWindowTextLength(hWnd);
            if (length == 0)
            {
                return string.Empty;
            }

            var sb = new StringBuilder(length + 1);
            if (option == EncodingOptions.Unicode)
            {
                DesktopNativeMethods.GetWindowText(hWnd, sb, sb.Capacity);
            }
            else
            {
                DesktopNativeMethods.GetWindowTextA(hWnd, sb, sb.Capacity);
            }

            return sb.ToString();
        }




        /// <summary>
        /// 在所有顶级窗口中查找符合条件的窗口句柄,只在顶级窗口中搜索,不会深入到子窗口,成功返回句柄,失败返回IntPtr.Zero
        /// 详情参见:https://msdn.microsoft.com/en-us/library/windows/desktop/ms633499(v=vs.85).aspx
        /// </summary>
        /// <param name="lpClassName">要查找的窗口类名(约等于进程名),为null或空时模糊查找</param>
        /// <param name="lpWindowName">要查找的窗口标题名,为null或空时模糊查找</param>
        /// <returns>成功返回句柄,失败返回IntPtr.Zero</returns>
        public static IntPtr FindWindow(string lpClassName, string lpWindowName)
        {
            return DesktopNativeMethods.FindWindow(lpClassName, lpWindowName);
        }

        /// <summary>
        /// 在指定父窗口及其子窗口中查找符合条件的窗口句柄,成功返回句柄,失败返回IntPtr.Zero
        /// </summary>
        /// <param name="hwndParent">要查找的父窗口句柄</param>
        /// <param name="hwndChildAfter">指定在父窗口中起始位置</param>
        /// <param name="lpszClass">要查找的窗口类名,为null或空时模糊查找</param>
        /// <param name="lpszWindow">要查找的窗口标题名,为null或空时模糊查找</param>
        /// <returns>成功返回句柄,失败返回IntPtr.Zero</returns>
        public static IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow)
        {
            return DesktopNativeMethods.FindWindowEx(hwndParent, hwndChildAfter, lpszClass, lpszWindow);
        }

        /// <summary>
        /// 查找当前用户空间下所有符合条件的窗口。如果不指定条件，将仅查找可见窗口。
        /// </summary>
        /// <param name="match">过滤窗口的条件。如果设置为 null，将仅查找可见窗口。</param>
        /// <returns>找到的所有窗口信息。</returns>
        public static List<WindowInfo> FindWindows(Predicate<WindowInfo> match = null)
        {
            if (match == null)
            {
                match = x => x.IsVisible && x.Title.Length > 0;
            }

            List<WindowInfo> windowList = null;
            DesktopNativeMethods.EnumWindows(OnWindowEnum, IntPtr.Zero);
            return windowList.FindAll(match);

            bool OnWindowEnum(IntPtr hWnd, IntPtr lparam)
            {
                // 仅查找顶层窗口。
                if (DesktopNativeMethods.GetParent(hWnd) == IntPtr.Zero)
                {
                    const int CAPCITY = 512;
                    // 获取窗口类名。
                    var lpString = new StringBuilder(CAPCITY);
                    DesktopNativeMethods.GetClassName(hWnd, lpString, lpString.Capacity);
                    var className = lpString.ToString();

                    // 获取窗口标题。
                    var lptrString = new StringBuilder(CAPCITY);
                    DesktopNativeMethods.GetWindowText(hWnd, lptrString, lptrString.Capacity);
                    var title = lptrString.ToString().Trim();

                    // 获取窗口可见性。
                    var isVisible = DesktopNativeMethods.IsWindowVisible(hWnd);

                    // 获取窗口位置和尺寸。
                    WindowLPRECT rect = default;
                    DesktopNativeMethods.GetWindowRect(hWnd, ref rect);
                    var bounds = new Rectangle(rect.Left, rect.Top, rect.Right - rect.Left, rect.Bottom - rect.Top);

                    int proId;
                    var ret = WindowHelper.GetWindowThreadProcessId(hWnd, out proId);

                    // 添加到已找到的窗口列表。
                    if (windowList == null)
                    {
                        windowList = new List<WindowInfo>();
                    }
                    windowList.Add(new WindowInfo(hWnd, className, title, isVisible, bounds, proId));
                }

                return true;
            }
        }

        /// <summary>
        /// 获取指定进程的所有窗口句柄
        /// </summary>
        /// <param name="process">进程</param>
        /// <param name="option">字符编码选项</param>
        /// <returns></returns>
        public static List<NameValue<IntPtr>> GetProcessWindowHandles(Process process, EncodingOptions option = EncodingOptions.Unicode)
        {
            var handles = new List<NameValue<IntPtr>>();
            IntPtr inputParam = new IntPtr(process.Id);

            // 遍历进程所有线程
            foreach (ProcessThread thread in process.Threads)
            {
                // 枚举线程关联的所有窗口
                DesktopNativeMethods.EnumChildWindows(IntPtr.Zero, (hWnd, lParam) =>
                {
                    int ownerProcessId = (int)lParam;
                    int processId;
                    int createWindowThreadId = GetWindowThreadProcessId(hWnd, out processId);
                    if (processId == ownerProcessId && DesktopNativeMethods.IsWindowVisible(hWnd))
                    {
                        for (int i = 0; i < handles.Count; i++)
                        {
                            if (handles[i].Value == hWnd)
                            {
                                return true;
                            }
                        }

                        // 可选过滤可见窗口
                        string caption = WindowHelper.GetWindowTitle(hWnd, option);
                        handles.Add(new NameValue<IntPtr>(caption, hWnd));
                    }
                    return true; // 继续枚举
                }, inputParam);
            }
            return handles.ToList();
        }

        /// <summary>
        /// 根据句柄查询该句柄对应的进程ID
        /// </summary>
        /// <param name="hwnd">应用程序句柄</param>
        /// <param name="processId">进程ID</param>
        /// <returns>创建窗口的线程ID</returns>
        public static int GetWindowThreadProcessId(IntPtr hwnd, out int processId)
        {
            return DesktopNativeMethods.GetWindowThreadProcessId(hwnd, out processId);
        }






        /// <summary>
        /// 刷新系统缓存图标
        /// Notifies the system of an event that an application has performed. An application should use this function if it performs an action that may affect the Shell. 
        /// </summary>
        /// <param name="wEventId">Describes the event that has occurred. Typically, only one event is specified at a time. If more than one event is specified, the values contained in the dwItem1 and dwItem2 parameters must be the same, respectively, for all specified events. This parameter can be one or more of the following values. </param>
        /// <param name="uFlags">Flags that indicate the meaning of the dwItem1 and dwItem2 parameters. The uFlags parameter must be one of the following values.</param>
        /// <param name="dwItem1">First event-dependent value. </param>
        /// <param name="dwItem2">Second event-dependent value.</param>
        public static void SHChangeNotify(Int32 wEventId, UInt32 uFlags, IntPtr dwItem1, IntPtr dwItem2)
        {
            DesktopNativeMethods.SHChangeNotify(wEventId, uFlags, dwItem1, dwItem2);
        }

        /// <summary>
        /// 最小化窗口在任务栏闪烁一次
        /// </summary>
        /// <param name="hwnd">窗口句柄</param>
        /// <param name="bInvert">true:表示窗口从一个状态闪烁到另一个状态;false:表示窗口恢复到初始状态（可能是激活的也可能是非激活的）</param>
        /// <returns>表示调用FlashWindow函数之前窗口的活动状态，若指定窗口在调用函数之前是激活的，那么返回非零值，否则返回零值</returns>
        public static bool FlashWindow(IntPtr hwnd, bool bInvert)
        {
            return DesktopNativeMethods.FlashWindow(hwnd, bInvert);
        }





        /// <summary>
        /// 判断系统是否认为指定的应用程序未响应.如果应用程序未等待输入、不在启动处理中，并且未在内部超时期限内调用PeekMessage,则被识为不响应.
        /// [如果窗口停止响应则返回true;否则返回false]
        /// </summary>
        /// <param name="processId">进程ID</param>
        /// <returns>如果窗口停止响应则返回true;否则返回false</returns>
        public static bool IsNoResponse(int processId)
        {
            Process process = Process.GetProcessById(processId);
            if (process == null)
            {
                return true;
            }
            else
            {
                return DesktopNativeMethods.IsHungAppWindow(process.MainWindowHandle);
            }
        }

        /// <summary>
        /// 判断系统是否认为指定的应用程序未响应.如果应用程序未等待输入、不在启动处理中，并且未在内部超时期限内调用PeekMessage,则被识为不响应.
        /// [如果窗口停止响应则返回true;否则返回false]
        /// </summary>
        /// <param name="process">进程</param>
        /// <returns>如果窗口停止响应则返回true;否则返回false</returns>
        public static bool IsNoResponse(Process process)
        {
            if (process == null)
            {
                return true;
            }
            else
            {
                return DesktopNativeMethods.IsHungAppWindow(process.MainWindowHandle);
            }
        }

        /// <summary>
        /// 判断系统是否认为指定的应用程序未响应.如果应用程序未等待输入、不在启动处理中，并且未在内部超时期限内调用PeekMessage,则被识为不响应.
        /// [如果窗口停止响应则返回true;否则返回false]
        /// </summary>
        /// <param name="hwnd">窗口句柄</param>
        /// <returns>如果窗口停止响应则返回true;否则返回false</returns>
        public static bool IsNoResponse(IntPtr hwnd)
        {
            return DesktopNativeMethods.IsHungAppWindow(hwnd);
        }

        /// <summary>
        /// 向指定窗口发送消息
        /// </summary>
        /// <param name="hwnd">接收消息的窗口句柄</param>
        /// <param name="wMsg">要发送的消息,在类WindowMessages中定义了绝大部分消息,也许是全的,以后缺少啥就补充啥,慢慢补充</param>
        /// <param name="wParam">其它的消息特定信息</param>
        /// <param name="lParam">其它的消息特定信息</param>
        /// <returns>返回值指定消息处理的结果,这取决于发送的消息</returns>
        public static int SendMessage(IntPtr hwnd, int wMsg, int wParam, int lParam)
        {
            return DesktopNativeMethods.SendMessage(hwnd, wMsg, wParam, lParam);
        }



        #region 最小化窗口在任务栏闪烁

        /// <summary>
        /// 创建窗口闪烁对象信息
        /// </summary>
        /// <param name="handle">窗口够本</param>
        /// <param name="flags">The Flash Status.</param>
        /// <param name="count">次数</param>
        /// <param name="timeout">The rate at which the Window is to be flashed, in milliseconds. If Zero, the function uses the default cursor blink rate.</param>
        /// <returns></returns>
        private static FLASHWINFO Create_FLASHWINFO(IntPtr handle, uint flags, uint count, uint timeout)
        {
            FLASHWINFO fi = new FLASHWINFO();
            fi.cbSize = Convert.ToUInt32(System.Runtime.InteropServices.Marshal.SizeOf(fi));
            fi.hwnd = handle;
            fi.dwFlags = flags;
            fi.uCount = count;
            fi.dwTimeout = timeout;
            return fi;
        }

        /// <summary>
        /// 任务栏窗口闪烁直到该窗口接收到焦点为止
        /// </summary>
        /// <param name="hwnd">窗口句柄</param>
        /// <returns></returns>
        public static bool Flash(IntPtr hwnd)
        {
            // Make sure we're running under Windows 2000 or later
            if (DesktopNativeMethods.Win2000OrLater)
            {
                FLASHWINFO fi = Create_FLASHWINFO(hwnd, DesktopNativeMethods.FLASHW_ALL | DesktopNativeMethods.FLASHW_TIMERNOFG, uint.MaxValue, 0);
                return DesktopNativeMethods.FlashWindowEx(ref fi);
            }
            return false;
        }

        /// <summary>
        /// Flash the specified Window (form) for the specified number of times
        /// </summary>
        /// <param name="hwnd">窗口句柄</param>
        /// <param name="count">闪烁次数</param>
        /// <returns></returns>
        public static bool Flash(IntPtr hwnd, uint count)
        {
            if (DesktopNativeMethods.Win2000OrLater)
            {
                FLASHWINFO fi = Create_FLASHWINFO(hwnd, DesktopNativeMethods.FLASHW_ALL, count, 0);
                return DesktopNativeMethods.FlashWindowEx(ref fi);
            }
            return false;
        }

        /// <summary>
        /// Start Flashing the specified Window (form)
        /// </summary>
        /// <param name="hwnd">窗口句柄</param>
        /// <returns></returns>
        public static bool Start(IntPtr hwnd)
        {
            if (DesktopNativeMethods.Win2000OrLater)
            {
                FLASHWINFO fi = Create_FLASHWINFO(hwnd, DesktopNativeMethods.FLASHW_ALL, uint.MaxValue, 0);
                return DesktopNativeMethods.FlashWindowEx(ref fi);
            }
            return false;
        }

        /// <summary>
        /// Stop Flashing the specified Window (form)
        /// </summary>
        /// <param name="hwnd">窗口句柄</param>
        /// <returns></returns>
        public static bool Stop(IntPtr hwnd)
        {
            if (DesktopNativeMethods.Win2000OrLater)
            {
                FLASHWINFO fi = Create_FLASHWINFO(hwnd, DesktopNativeMethods.FLASHW_STOP, uint.MaxValue, 0);
                return DesktopNativeMethods.FlashWindowEx(ref fi);
            }
            return false;
        }

        #endregion



        #region 窗口淡入淡出

        /// <summary>
        /// 窗口淡入淡出
        /// </summary>
        /// <param name="hwnd">窗口句柄</param>
        /// <param name="dwTime">动画持续时间</param>
        /// <param name="dwFlags">动画类型(WindowAnimateType中的值按位枚举运算)</param>
        /// <returns>结果</returns>
        public static bool WindowFadeInOut(IntPtr hwnd, int dwTime, int dwFlags)
        {
            return DesktopNativeMethods.AnimateWindow(hwnd, dwTime, dwFlags);
        }

        /// <summary>
        /// 窗口淡入
        /// </summary>
        /// <param name="hwnd">窗口句柄</param>
        /// <param name="dwTime">动画持续时间</param>
        /// <param name="dwFlags">动画类型(WindowAnimateType中的值按位枚举运算)</param>
        /// <returns>结果</returns>
        public static bool WindowFadeIn(IntPtr hwnd, int dwTime = 300, int dwFlags = WindowAnimateType.AW_BLEND)
        {
            return DesktopNativeMethods.AnimateWindow(hwnd, dwTime, dwFlags);
        }

        /// <summary>
        /// 窗口淡出
        /// </summary>
        /// <param name="hwnd">窗口句柄</param>
        /// <param name="dwTime">动画持续时间</param>
        /// <param name="dwFlags">动画类型(WindowAnimateType中的值按位枚举运算)</param>
        /// <returns>结果</returns>
        public static bool WindowFadeOut(IntPtr hwnd, int dwTime = 300, int dwFlags = WindowAnimateType.AW_SLIDE | WindowAnimateType.AW_HIDE | WindowAnimateType.AW_BLEND)
        {
            return DesktopNativeMethods.AnimateWindow(hwnd, dwTime, dwFlags);
        }

        #endregion



        /// <summary>
        /// 设置应用程序的父窗口
        /// </summary>
        /// <param name="hWndChild">子窗口句柄</param>
        /// <param name="hWndNewParent">父窗口句柄</param>
        public static void SetParent(IntPtr hWndChild, IntPtr hWndNewParent)
        {
            IntPtr result = DesktopNativeMethods.SetParent(hWndChild, hWndNewParent);
            if (result == IntPtr.Zero)
            {
                throw new OperationFailException($"设置父窗口失败，错误信息:{OSSystemApis.GetLastErrorInfo()}");
            }
        }

        /// <summary>
        /// 设置窗口属性
        /// </summary>
        /// <param name="hwnd">要移除边框的应用程序句柄</param>
        /// <param name="nIndex">索引</param>
        /// <param name="dwNewLong">边框值</param>
        /// <returns>则返回值为执行操作前的值</returns>
        public static int SetWindowLongPtr(IntPtr hwnd, int nIndex, int dwNewLong)
        {
            int result = DesktopNativeMethods.SetWindowLongPtr(hwnd, nIndex, dwNewLong);
            if (result == 0)
            {
                throw new OperationFailException($"设置窗口属性失败，错误信息:{OSSystemApis.GetLastErrorInfo()}");
            }

            return result;
        }

        /// <summary>
        /// 获取指定窗口句柄对应窗口的位置和尺寸
        /// </summary>
        /// <param name="hWnd">窗口句柄</param>
        /// <returns>获取成功返回窗口的位置和尺寸,失败返回null</returns>
        public static WindowLPRECT? GetWindowRect(IntPtr hWnd)
        {
            WindowLPRECT rect = new WindowLPRECT();
            if (DesktopNativeMethods.GetWindowRect(hWnd, ref rect))
            {
                return rect;
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// 移动应用程序窗口位置
        /// </summary>
        /// <param name="hwnd">程序窗口句柄</param>
        /// <param name="x">x坐标</param>
        /// <param name="y">y坐标</param>
        /// <param name="cx">宽度</param>
        /// <param name="cy">高度</param>
        /// <param name="repaint">是否修正</param>
        /// <returns>bool</returns>
        public static bool MoveWindow(IntPtr hwnd, int x, int y, int cx, int cy, bool repaint)
        {
            return DesktopNativeMethods.MoveWindow(hwnd, x, y, cx, cy, repaint);
        }

        /// <summary>
        /// 设置窗口可见性
        /// </summary>
        /// <param name="hwnd">程序窗口句柄</param>
        /// <param name="visible">true:显示;false:隐藏</param>
        public static void SetWindowVisible(IntPtr hwnd, bool visible)
        {
            DesktopNativeMethods.ShowWindow(hwnd, visible ? 1 : 0);
        }


    }
}
